/*
 * 文件名：Exceptions.java
 * 版权：MoonBox 
 * 修改人：Peter
 * 修改时间：2018年10月11日
 * 修改内容：新增
 */
package com.renzhebiao.lkang.utils;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 关于异常的工具类.
 * 
 * @author Peter
 * @since 1.0.0
 */
public class Exceptions {

    /**
     * 将CheckedException转换为UncheckedException.
     * 
     * @param e Exception
     * @return RuntimeException
     */
    public static RuntimeException unchecked(Exception e) {
        if (e instanceof RuntimeException) {
            return (RuntimeException) e;
        } else {
            return new RuntimeException(e);
        }
    }

    /**
     * 将ErrorStack转化为String.
     * 
     * @param e Exception
     * @return 异常堆栈信息
     */
    public static String getStackTraceAsString(Exception e) {
        StringWriter stringWriter = new StringWriter();
        e.printStackTrace(new PrintWriter(stringWriter));
        return stringWriter.toString();
    }

    /**
     * 判断异常是否由某些底层的异常引起.
     * 
     * @param ex Exception
     * @param causeExceptionClasses Exception
     * @return boolean
     */
    @SafeVarargs
    public static boolean isCausedBy(Exception ex, Class<? extends Exception>... causeExceptionClasses) {
        Throwable cause = ex;
        while (cause != null) {
            for (Class<? extends Exception> causeClass : causeExceptionClasses) {
                if (causeClass.isInstance(cause)) {
                    return true;
                }
            }
            cause = cause.getCause();
        }
        return false;
    }

    /**
     * 获取Throwable中的堆栈信息，用户日志打印
     * 
     * @param t Throwable
     * @return 堆栈信息
     */
    public static String getStackTrace(Throwable t) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw, true);
        t.printStackTrace(pw);
        pw.flush();
        sw.flush();
        return sw.toString();
    }

    /**
     * Throw a checked exception without adding the exception to the throws clause of the calling method. This method
     * prevents throws clause pollution and reduces the clutter of "Caused by" exceptions in the stacktrace.
     * <p>
     * The use of this technique may be controversial, but exceedingly useful to library developers. <code>
     *  public int propagateExample { // note that there is no throws clause
     *      try {
     *          return invocation(); // throws IOException
     *      } catch (Exception e) {
     *          return ExceptionUtils.rethrow(e);  // propagates a checked exception
     *      }
     *  }
     * </code>
     * <p>
     * This is an alternative to the more conservative approach of wrapping the checked exception in a RuntimeException:
     * <code>
     *  public int wrapExample { // note that there is no throws clause
     *      try {
     *          return invocation(); // throws IOException
     *      } catch (Error e) {
     *          throw e;
     *      } catch (RuntimeException e) {
     *          throw e;  // wraps a checked exception
     *      } catch (Exception e) {
     *          throw new UndeclaredThrowableException(e);  // wraps a checked exception
     *      }
     *  }
     * </code>
     * <p>
     * One downside to using this approach is that the java compiler will not allow invoking code to specify a checked
     * exception in a catch clause unless there is some code path within the try block that has invoked a method
     * declared with that checked exception. If the invoking site wishes to catch the shaded checked exception, it must
     * either invoke the shaded code through a method re-declaring the desired checked exception, or catch Exception and
     * use the instanceof operator. Either of these techniques are required when interacting with non-java jvm code such
     * as Jython, Scala, or Groovy, since these languages do not consider any exceptions as checked.
     *
     * @param throwable The throwable to rethrow.
     * @param <R> The type of the returned value.
     * @return Never actually returned, this generic type matches any type which the calling site requires. "Returning"
     * the results of this method, as done in the propagateExample above, will satisfy the java compiler requirement
     * that all code paths return a value.
     * @since 3.5
     * @see #wrapAndThrow(Throwable)
     */
    public static <R> R rethrow(final Throwable e) {
        // claim that the typeErasure invocation throws a RuntimeException
        return Exceptions.<R, RuntimeException> typeErasure(e);
    }

    /**
     * Claim a Throwable is another Exception type using type erasure. This hides a checked exception from the java
     * compiler, allowing a checked exception to be thrown without having the exception in the method's throw clause.
     */
    @SuppressWarnings("unchecked")
    private static <R, T extends Throwable> R typeErasure(final Throwable throwable) throws T {
        throw (T) throwable;
    }

    /**
     * <p>
     * Introspects the <code>Throwable</code> to obtain the root cause.
     * </p>
     * <p>
     * This method walks through the exception chain to the last element, "root" of the tree, using
     * {@link #getCause(Throwable)}, and returns that exception.
     * </p>
     * <p>
     * From version 2.2, this method handles recursive cause structures that might otherwise cause infinite loops. If
     * the throwable parameter has a cause of itself, then null will be returned. If the throwable parameter cause chain
     * loops, the last element in the chain before the loop is returned.
     * </p>
     *
     * @param throwable the throwable to get the root cause for, may be null
     * @return the root cause of the <code>Throwable</code>, <code>null</code> if none found or null throwable input
     */
    public static Throwable getRootCause(final Throwable throwable) {
        final List<Throwable> list = getThrowableList(throwable);
        return list.size() < 2 ? null : list.get(list.size() - 1);
    }

    /**
     * <p>
     * Finds a <code>Throwable</code> by method name.
     * </p>
     *
     * @param throwable the exception to examine
     * @param methodName the name of the method to find and invoke
     * @return the wrapped exception, or <code>null</code> if not found
     */
    // TODO: Remove in Lang 4.0
    private static Throwable getCauseUsingMethodName(final Throwable throwable, final String methodName) {
        Method method = null;
        try {
            method = throwable.getClass().getMethod(methodName);
        } catch (final NoSuchMethodException | SecurityException ignored) { // NOPMD
            // exception ignored
        }

        if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
            try {
                return (Throwable) method.invoke(throwable);
            } catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) { // NOPMD
                // exception ignored
            }
        }
        return null;
    }

    /**
     * <p>
     * Returns the list of <code>Throwable</code> objects in the exception chain.
     * </p>
     * <p>
     * A throwable without cause will return a list containing one element - the input throwable. A throwable with one
     * cause will return a list containing two elements. - the input throwable and the cause throwable. A
     * <code>null</code> throwable will return a list of size zero.
     * </p>
     * <p>
     * This method handles recursive cause structures that might otherwise cause infinite loops. The cause chain is
     * processed until the end is reached, or until the next item in the chain is already in the result set.
     * </p>
     *
     * @param throwable the throwable to inspect, may be null
     * @return the list of throwables, never null
     * @since Commons Lang 2.2
     */
    public static List<Throwable> getThrowableList(Throwable throwable) {
        final List<Throwable> list = new ArrayList<>();
        while (throwable != null && list.contains(throwable) == false) {
            list.add(throwable);
            throwable = throwable.getCause();
        }
        return list;
    }

}
